package com.wits.media;

import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.os.Environment;
import android.util.Log;
import android.view.SurfaceHolder;

import com.wits.av.MFormat;
import com.wits.av.MPacket;
import com.wits.av.MediaSink;
import com.wits.av.MediaType;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.List;
import android.media.MediaFormat;


/**
 * Created by zhengboyuan on 2018-03-13.
 */

public class CameraEncoder implements MediaEncoder,
        Camera.PreviewCallback,
        Camera.AutoFocusCallback {

    private static final String TAG = "PsCamera";

    private byte[]  codecConfig;
    private OsdOverlayMixer overlayMixer;

    protected int rotation = 0;
    protected int orientation = 0;

    protected LinearTimeStamp timeStamp = new LinearTimeStamp();

    public CameraEncoder() {
    }

    public void setOverlayMixer(OsdOverlayMixer overlayMixer) {
        this.overlayMixer = overlayMixer;
    }

    public void setSurfaceHolder(SurfaceHolder holder) {
        mHolder = holder;
    }

    public void setCamera(int camID) {
        mCameraID = camID;
    }

    public void setRotation(int rotation) {
        this.rotation = rotation;
    }

    public void setDisplayOrientation(int degree) {
        this.orientation = degree;
    }


    @Override
    public boolean open(MFormat fmt) {
        Log.i(TAG, "CameraEncoder.open");

        if (!fmt.check()) {
            return false;
        }

        mFormat = fmt;
        mFormat.clockRate = 1000000;

        yuv420 = new byte[mFormat.width * mFormat.height * 3/2];

        if (!openCamera(mFormat)) {
            close();
            return false;
        }

        try {

            Log.i(TAG, "CameraEncoder before createVideoCodec");

            mEncoder = createVideoCodec();

            Log.i(TAG, "CameraEncoder.before start");

            mEncoder.start();

            Log.i(TAG, "CameraEncoder.open end");

            return true;
        } catch (Throwable t) {
            t.printStackTrace();

            close();
        }

        return false;
    }

    @Override
    public void close() {
        Log.i(TAG, "CameraEncoder.close");

        releaseCamera();

        if (mEncoder != null) {
            mEncoder.release();
            mEncoder = null;
        }

        Log.i(TAG, "CameraEncoder.close end");
    }

    @Override
    public boolean isOpen() {
        return (mCamera != null);
    }

    @Override
    public boolean start() {
        Log.i(TAG, "CameraEncoder.start");

        timeStamp.reset();

        if (mCamera != null) {
            mCamera.startPreview();

            mCamera.cancelAutoFocus();
        }

        Log.i(TAG, "CameraEncoder.start end");

        return true;
    }

    @Override
    public void stop() {
        Log.i(TAG, "CameraEncoder.stop");

        if (mCamera != null) {
            mCamera.setPreviewCallback(null);
            mCamera.stopPreview();
        }

        m_lastTime = 0;
        timeStamp.reset();

        Log.i(TAG, "CameraEncoder.stop end");
    }

    @Override
    public boolean isStarted() {
        return false;
    }

    @Override
    public MFormat getFormat() {
        return mFormat;
    }

    @Override
    public void setSink(MediaSink sink) {
        mSink = sink;
    }


    public void autoFocus() {
        if (mCamera != null) {
            mCamera.autoFocus(this);
        }
    }



    protected MediaCodec createVideoCodec() throws IOException {
        String codecName = MFormat.getMime(mFormat.codec);
        MediaCodec encoder = MediaCodec.createEncoderByType(codecName);

        Log.i(TAG, encoder.getName());

        mColorFormat = chooseColorFormat(encoder.getName());

        MediaFormat encoderFormat = MediaFormat.createVideoFormat(codecName, mFormat.width, mFormat.height);

        encoderFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, mColorFormat);

        if (mFormat.bitrate > 0) {
            encoderFormat.setInteger(MediaFormat.KEY_BIT_RATE, mFormat.bitrate);
        }

        encoderFormat.setInteger(MediaFormat.KEY_FRAME_RATE, mFormat.fps);

        int iFrameInterval = (mFormat.gop > 0) ? mFormat.gop : 1;
        encoderFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, iFrameInterval);

        /// 目前发现5.x,6.x 不支持
//        if (codecName.equals(MediaFormat.MIMETYPE_VIDEO_AVC)) {
//            int profile = getAvcProfile(mFormat.profile);
//            encoderFormat.setInteger(MediaFormat.KEY_PROFILE, profile);
//        }

        encoder.configure(encoderFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
        return encoder;
    }

    protected int getAvcProfile(int profile) {
        int avcProfile = MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline;
        if (profile == MediaType.H264_PROFILE_HIGH) {
            avcProfile = MediaCodecInfo.CodecProfileLevel.AVCProfileHigh;
        } else if (profile == MediaType.H264_PROFILE_MAIN) {
            avcProfile = MediaCodecInfo.CodecProfileLevel.AVCProfileMain;
        }
        return avcProfile;
    }

    protected MediaCodecInfo findCodecInfo(String name) {
        final int mediaCodecListCount = MediaCodecList.getCodecCount();
        for (int i = 0; i < mediaCodecListCount; i ++) {
            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
            if (codecInfo.getName().equals(name)) {
                return codecInfo;
            }
        }
        return null;
    }

    protected int chooseColorFormat(String name) {
        int fmt = MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar;
        MediaCodecInfo codecInfo = findCodecInfo(name);
        if (codecInfo != null) {
            String[] types = codecInfo.getSupportedTypes();
            MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(types[0]);

            int[] priorityFormats = new int[] {
                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar,
                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar,
                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible
            };

            boolean found = false;
            for (int priorityFormat : priorityFormats) {
                for (int colorFormat: capabilities.colorFormats) {
                    if (priorityFormat == colorFormat) {
                        fmt = colorFormat;
                        found = true;
                        break;
                    }
                }

                if (found) {
                    break;
                }
            }
        }
        return fmt;
    }


    private MediaCodec mEncoder;
    protected MFormat mFormat;
    protected MediaSink mSink;
    protected Camera mCamera;
    protected SurfaceHolder mHolder;
    private byte[] yuv420 = null;
    private long	m_lastTime;
    private int mCameraID;
    private int mColorFormat;

    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {

        drawOverlay(data);

        //Log.i(TAG, "onPreviewFrame " + data.length + " width: ");

        if (mColorFormat == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible) {
            swapYV12toI420(data, yuv420, mFormat.width, mFormat.height);
            inputData(yuv420);
        } else if (mColorFormat == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar) {
            swapYUV420SemiPlanar(data, yuv420, mFormat.width, mFormat.height);
            inputData(yuv420);
        } else if (mColorFormat == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar) {
            swapYV12toI420(data, yuv420, mFormat.width, mFormat.height);
            inputData(yuv420);
        }

        try {
            fetchOut();
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    //yv12 转 yuv420p  yvu -> yuv
    private void swapYV12toI420(byte[] yv12bytes, byte[] i420bytes, int width, int height) {
        System.arraycopy(yv12bytes, 0, i420bytes, 0,width*height);
        System.arraycopy(yv12bytes, width*height+width*height/4, i420bytes, width*height,width*height/4);
        System.arraycopy(yv12bytes, width*height, i420bytes, width*height+width*height/4,width*height/4);
    }

    private void swapYUV420SemiPlanar(byte[] data, byte[] i420bytes, int width, int height) {
        int frameSize = width *  height;
        int qFrameSize = frameSize / 4;
        System.arraycopy(data, 0, yuv420, 0, frameSize);
        for (int i = 0; i < (qFrameSize); i++) {
            yuv420[frameSize + i*2] = (data[frameSize + qFrameSize + i]);
            yuv420[frameSize + i*2 + 1] = (data[frameSize + i]);
        }
    }

    private void inputData(byte[] frame) {
        try {

            ByteBuffer[] inputBuffers = mEncoder.getInputBuffers();
            int inputBufferIndex = mEncoder.dequeueInputBuffer(1000 * 5);
            if (inputBufferIndex >= 0) {
                ByteBuffer inputBuffer = inputBuffers[inputBufferIndex];
                inputBuffer.clear();

                long pts = timeStamp.get();

                if(inputBuffer.capacity() < frame.length){
                    mEncoder.queueInputBuffer(inputBufferIndex, 0, 0, pts, 0);
                    return;
                }

                inputBuffer.put(frame);

                mEncoder.queueInputBuffer(inputBufferIndex, 0, frame.length, pts, 0);
            }
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    private void fetchOut() {
        ByteBuffer[] outputBuffers = mEncoder.getOutputBuffers();
        MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
        int outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 0);

        boolean codecConfigSent = false;
        while (outputBufferIndex >= 0)
        {
            ByteBuffer outputBuffer = outputBuffers[outputBufferIndex];
            byte[] outData = new byte[bufferInfo.size];
            outputBuffer.get(outData);

            long pts = bufferInfo.presentationTimeUs;

            int duration = 0;
            if (m_lastTime > 0) {
                duration = (int) (pts - m_lastTime);
            }
            m_lastTime = pts;

            int flags = 0;

            if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_CODEC_CONFIG) {
                flags = MediaType.AV_PKT_FLAG_KEY;

                codecConfig = outData;
                codecConfigSent = false;
            } else {
                flags = bufferInfo.flags;
            }

            MPacket pkt = new MPacket(MediaType.MEDIA_TYPE_VIDEO, outData, pts, flags);
            pkt.setDuration(duration);

            //String text = String.format("pkt(%d, %d, %d, %d)", outData.length, duration, bufferInfo.presentationTimeUs, pkt.flags);
            //Log.i(TAG, text);

            // 关键帧
            if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_KEY_FRAME)
            {
                if (!codecConfigSent && codecConfig != null) {
                    MPacket configPkt = new MPacket(MediaType.MEDIA_TYPE_VIDEO, codecConfig, pts, MediaType.AV_PKT_FLAG_KEY);
                    firePacket(configPkt);
                }
            }

            firePacket(pkt);

            //dumpData(outData);

            mEncoder.releaseOutputBuffer(outputBufferIndex, false);
            outputBufferIndex = mEncoder.dequeueOutputBuffer(bufferInfo, 0);
        }
    }


    private void dumpData(byte[] data) {
        String root = Environment.getExternalStorageDirectory().toString();
        File myDir = new File(root + "/Download");
        myDir.mkdirs();

        String filename = "camera.h264";
        File file = new File(myDir, filename);
        FileOutputStream out = null;
        try {
            out = new FileOutputStream(file, true);
            out.write(data);
            out.flush();
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onAutoFocus(boolean success, Camera camera) {
        if (success) {
            camera.cancelAutoFocus();
        }
    }


    private void setAutoFocus(Camera.Parameters parameters) {
        List<String> modes = parameters.getSupportedFocusModes();
        for (String mode: modes) {
            if (mode == Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO) {
                parameters.setFocusMode(mode);
                break;
            } else if (mode == Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE) {
                parameters.setFocusMode(mode);
                break;
            } else if (mode == Camera.Parameters.FOCUS_MODE_AUTO) {
                parameters.setFocusMode(mode);
                break;
            } else if (mode == Camera.Parameters.FOCUS_MODE_FIXED) {
                parameters.setFocusMode(mode);
                break;
            }
        }

    }

    private void firePacket(MPacket pkt) {
        if (this.mSink != null) {
            this.mSink.onMediaPacket(pkt);
        }
    }

    public static boolean isSupported(Camera camera, int width, int height) {
        Camera.Parameters parameters = camera.getParameters();

        List<Camera.Size> sizeList = parameters.getSupportedPictureSizes();
        for (Camera.Size size: sizeList) {
            if (size.width == width && size.height == height) {
                return true;
            }
        }
        return false;
    }

    private void releaseCamera() {
        if (mCamera != null) {
            mCamera.release();
            mCamera = null;
        }
    }

    private void drawOverlay(byte[] data) {
        if (overlayMixer != null) {
            ImageFrame imageFrame = new ImageFrame(data, ImageFormat.YV12, mFormat.width, mFormat.height);
            overlayMixer.draw(imageFrame);
        }
    }

    private boolean openCamera(MFormat fmt) {
        try {
            mHolder.setFixedSize(fmt.width, fmt.height);
            //mHolder.addCallback(this);

            mCamera = Camera.open(mCameraID);
            mCamera.setPreviewDisplay(mHolder);
            //mCamera.setPreviewTexture(null);

            Camera.Parameters parameters = mCamera.getParameters();

            if (!isSupported(mCamera, fmt.width, fmt.height)) {
                releaseCamera();
                return false;
            }

            parameters.setPreviewSize(fmt.width, fmt.height);
            parameters.setPictureSize(fmt.width, fmt.height);
            parameters.setPreviewFormat(ImageFormat.YV12);
            //setAutoFocus(parameters);

            if (rotation > 0) {
                parameters.setRotation(rotation);
            }

            /// 在某个平板版本上, 导致 setParameters 失败.
            parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);

            if (orientation > 0) {
                mCamera.setDisplayOrientation(orientation);
            }

            mCamera.setParameters(parameters);
            mCamera.setPreviewCallback(this);

            return true;
        } catch (Throwable t) {
            t.printStackTrace();
        }
        return false;
    }


}
